package org.beanscreator;

import org.beanscreator.definitionreader.ClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.NoSuchElementException;

public class BeanFactoryImpl implements BeanFactory {

    private static final Logger LOGGER = LoggerFactory.getLogger(BeanFactoryImpl.class);

    private static final String BEAN_NOT_FOUND_EX = "There is no bean definition, corresponding to specified ID!";
    private static final String NO_NECESSARY_CONSTRUCTOR_EX = "There is no necessary constructor args definition!";

    private List<ClassDefinition> classDefList;

    public BeanFactoryImpl(List<ClassDefinition> classDefList) {

        this.classDefList = classDefList;
    }

    @Override
    public Object getBean(String string) {

        Object bean = null;

        ClassDefinition classDefinition = getNecessaryClassDef(string);

        try {
            Class beanClass = Class.forName(classDefinition.getFullClassName());

            List<Object> args = classDefinition.getConstructorArgs();
            Map<String, Object> params = classDefinition.getParamsValues();

            bean = instantiateBeanWithConstr(beanClass, args);

            for (Map.Entry<String, Object> item : params.entrySet()) {

                Object val = item.getValue();
                String valStr = val.toString();

                Field field = beanClass.getDeclaredField(item.getKey());
                field.setAccessible(true);

                if (valStr.length() > 1 && valStr.charAt(0) == '$') {
                    val = getRefBeanValue(val);
                }

                field.set(bean, val);
                field.setAccessible(false);
            }

        } catch (ClassNotFoundException e) {
            LOGGER.error("", e);
        } catch (InvocationTargetException e) {
            LOGGER.error("", e);
        } catch (InstantiationException e) {
            LOGGER.error("", e);
        } catch (IllegalAccessException e) {
            LOGGER.error("", e);
        } catch (NoSuchMethodException | NoSuchFieldException e) {
            LOGGER.error("", e);
        }

        return bean;
    }

    private Object getRefBeanValue(Object refBeanName) throws ClassNotFoundException {

        Class c = null;
        String beanId = refBeanName.toString().substring(1);

        for (ClassDefinition definition : classDefList) {
            if (definition.getId().equals(beanId)) {
                c = Class.forName(definition.getFullClassName());
            }
        }
        if (c == null) {
            throw new MissingResourceException("There is no bean with id = " + beanId + "!", "", "");
        }

        return getBean(beanId, c);
    }

    @Override
    public <T> T getBean(String string, Class<T> type) {

        Object bean = getBean(string);
        return (T) bean;
    }

    private Object instantiateBeanWithConstr(final Class beanClass, final List<Object> args)
            throws InstantiationException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {

        Object bean = null;

        Constructor[] availableContructors = beanClass.getConstructors();
        for (Constructor constr : availableContructors) {
            if (constr.getParameterTypes().length == args.size()) {
                bean = constr.newInstance(args.toArray());
            }
        }
        if (bean == null) {
            throw new NoSuchMethodException(NO_NECESSARY_CONSTRUCTOR_EX);
        }
        return bean;
    }

    private ClassDefinition getNecessaryClassDef(String string) {

        ClassDefinition classDefinition = null;

        for (ClassDefinition item : classDefList) {
            if (item.getId().equals(string)) {
                classDefinition = item;
                break;
            }
        }

        if (classDefinition == null) {
            throw new NoSuchElementException(BEAN_NOT_FOUND_EX);
        }
        return classDefinition;
    }
}
